2025-12-01

December Adventure

Trying to do the December Adventure thing again! I probaly won't have the time to do something everyday, I am also writing a thesis right now, but every other day should be doable, maybe.

Day 1

I am working on more or less the same thing as I was working on last time I tried this. The river Wayland server has a protocol to allow external clients to control window management.

I was trying to create a window manager for river in scheme, with a tiny core written in zig. I wanted scheme code to control all policy, without ever being able to cause a creash or protocol error. I used a generational list to store pointers to wayland objects, so that scheme code would only ever deal with the keys for that list. And the core checked all requests and would raise a scheme error if they were not allowed, rather than crash with a wayland protocol error.

All of this is nice, but something about it always annoyed me, as is evident by having rewritten the core completely about six times now…

tunnel.jpg

digging through old projects

So I am putting the scheme idea on hold for now. I am going to write a window manager in pure C, with all the features I had planned for my setup. That way I can finally get to interesting window management and UI bits rather than dealing with designing APIs, syncing lifetimes and wrangling FFIs. I'll return to outsourcing the policy to a lispy scripting language at a later date, probably.

Over the last few days I've already started to create the core of the window manager in about 2800 lines of C.

This leaves us with the interesting bits for this december adventure: Adding fancy UI and interesting window management policy!

Normally, using libwayland from C means dealing with a lot of boilerplate. Our corporate overlords really want us to be using their generative AI crap for that, but I am not falling for that and just wrote some elisp that auto-generates all the boilerplate.

I am going to use this first day to write about some of the architecture choices I've made which I consider interesting.

When libwayland sends you an event like wl_pointer.enter, it gives you a pointer to the wl_surface object the wl_pointer just entered ("is hovering over"). If you use multiple surfaces for very different use cases, for example for window decorations and for a desktop background, how do you know what your pointer is hovering over right now?

You could either loop through all of your wl_surface's. Or maybe you could store type information in its user data pointer. I decided to create a "super-class" for surfaces.

When I get a pointer to a wl_surface, I can get the Surface from the user data pointer. And based on the values of Surface.role I now what the parent object is.

enum SurfaceRole
{
        SR_DECORATION,
        SR_BACKGROUND,
};

struct Surface
{
        struct wl_surface *wl_surface;
        enum SurfaceRole role:4;
};

In C, the pointer to the first element of a struct can be cast safely to and from a pointer to the struct itself. Otherwise there is also wl_container_of().

My actual Surface struct also contains the wp_viewport and the wp_fractional_scale_v1 objects related to the wl_surface, since all my surfaces need them.

struct BackgroundSurface
{
        struct Surface srf;
        // ...
};

struct WindowDecorationSurface
{
        struct Surface srf;
        // ...
};

Also turns out the fractional scale handler does not need to know what kind of surface just changed its fractional scale. It just adds the Surface object itself to a list of "dirty" surfaces which need a new frame in the next render phase.

static void fractional_scale_handle_preferred_scale
(
        void *data,
        struct wp_fractional_scale_v1 *frac_scale,
        uint32_t scale
)
{
        struct Surface *surface = (struct Surface *)data;
        assert(surface->frac_scale == frac_scale);
        if ( scale == surface->scale )
                return;

        surface->scale = scale;

        wl_list_remove(&surface->link);
        wl_list_insert(&dirty_surfaces, &surface->link);
}

static void  wm_handle_render_start
(
        void *data,
        struct river_window_manager_v1 *river_window_manager_v1
)
{
        // ...
        struct Surface *srf, *_srf;
        wl_list_for_each_safe(srf, _srf, &dirty_surfaces, link)
        {
                switch (srf->role)
                {
                        case SR_DECORATION:
                                struct WindowDecorationSurface *ds = (struct WindowDecorationSurface *)srf;
                                window_decoration_surface_attach_buffer(ds);
                                break;

                        case SR_BACKGROUND:
                                struct BackgroundSurface *bs = (struct BackgroundSurface *)srf;
                                background_surface_attach_buffer(bs);
                                break;

                        default:
                                __builtin_unreachable();
                                assert(false);
                }

                wl_list_remove(&srf->link);

                /* Initialize again, so that removal is never UB. */
                wl_list_init(&srf->link);
        }
        assert(wl_list_empty(&dirty_surfaces));
        // ...
}

In general I want to avoid looping over all windows, outputs and seats for each manage phase and render phase. In realistic use cases, you'll have one seat, less than five outputs and less than fifty windows, so it doesn't really matter, but I kinda like the idea.

So I added a list of planned actions and created a PlannedAction "super-class" similar to how what I did for surfaces.

enum PlannedActionType
{
        PA_WINDOW_INIT, PA_WINDOW_DEINIT,
        PA_WINDOW_MOVE, PA_WINDOW_RESIZE,
        // ...
};

struct PlannedAction
{
        struct wl_list link;
        enum PlannedActionType type;
};

Now my structs for windows, outputs and seats contain these action structs. This required thinking about which actions can show up in parallel, requiring multiple action structs per object. This approach allows me to avoid allocations for scheduling actions, for a negligible increase of memory usage, which is kinda neat.

struct GeometryAction
{
        struct PlannedAction action;
        int32_t x, y;
};

struct WindowTargetAction
{
        struct PlannedAction action;
        struct RiverWindow *window;
};

There are more interesting bits, but I'll write about those when I get to finish / improve the corresponding systems. For example, when I create fancy window decorations, I'll show you how I am able to draw window decorations without having to re-render when the window resizes.

Day 2

I want to get rid of all places in the code where I iterate over all windows / seats / outputs to find a related object.

Currently listening to:

The Spoiled & Corlyx - Ravenous

Windows have a pointer to their parent window (or NULL if there is none). However to find the children of a window, I looped over all windows, checking if the current window is their parent.

Today I added a list of children to windows.

This mainly helped improve some code around interactively moving and resizing windows. Child windows are always kept centered on top of their parent window. This means we need to attach all windows in the "family" to a move and schedule update all child window positions for every resize. This code is now a lot clearer thanks to the child window list.

I have not yet encountered a window with more than a single child window, nor a child window which has it's own child window(s). Still, I decided to support that, up to 16 levels deep. I think it's always a good idea to put hard limits like that on iterations over linked lists which where constructed in response to events without any further validation. For example it prevents endless loops in the case of circular relationships and also makes you more aware of the resources your loop needs so you don't accidentally write some sort of overflow bug.

/* Find topmost parent. */
for (int i = 16; i > 0; i--)
{
        if ( NULL == w->parent )
                break;
        if ( REMOVED == w->parent->state )
                break;
        w = w->parent;
}

// Operate on topmost parent window here...

struct RiverWindow *wins[16] = { 0 };
wins[0] = w;
for (int n = 0, N = 1; N > n && N < 16; n++)
{
        wl_list_for_each(w, &(wins[n]->children), child_link)
        {
                /* Always good to be paranoid. */
                assert(w->state != REMOVED);
                assert(w != wins[n]);
                assert(w->parent != NULL);
                assert(w->parent == wins[n]);

                // Operate on child window here...

                if ( N < 16 && !wl_list_empty(&w->children) )
                {
                        wins[N] = w;
                        N++;
                }
        }
}

I did fight with one nasty bug! Normally the compiler will warn you when you accidentally place a semicolon after a for loop.

// This will raise an error:
for (int n = 0, N = 1; N > n && N < 16; n++);
{
        // ...
}

However it won't do that if the loop is hidden behind macro magic. This cost me a fair amount of time.

// This will not raise an error:
wl_list_for_each(w, &(wins[0]->children), child_link);
{
        // ...
}

To avoid future pain I made emacs to warn me of that bug:

(font-lock-add-keywords 'c-mode '(("\\(wl_list_for_each([[:blank:][:graph:]]*);\\)" . font-lock-warning-face)))
(font-lock-add-keywords 'c-mode '(("\\(wl_list_for_each_reverse([[:blank:][:graph:]]*);\\)" . font-lock-warning-face)))
(font-lock-add-keywords 'c-mode '(("\\(wl_list_for_each_safe([[:blank:][:graph:]]*);\\)" . font-lock-warning-face)))
(font-lock-add-keywords 'c-mode '(("\\(wl_list_for_each_reverse_safe([[:blank:][:graph:]]*);\\)" . font-lock-warning-face)))

Couldn't help but continue hacking on this…

Now you can lower an raise windows by scrolling on the window decorations. This required adding a small threshold, as otherwise using f.e. two-finger-scrolling on a touchpad would trigger more often than intentioned.

Currently listening to:

Social Youth Cult - The Lighthouse

By now I had three places in the code where I was iterating over all children of a window using a similar mechanism, so I created a macro to deduplicate this. Just like all other C preprocessor macros, it is not pretty, but it does its job.

Day 3

Today I added to windows a list of seats that focus it or plan to focus it. Maybe I am just high on my own supply here (the last few changes I've made all but one added more lists) but I genuinely think this results in better and more clear code, compared to encoding relations between objects as a simple pointer in one of them. In fact, the focus list already fixed a crash when multiple windows where closed in the same manage phase.

Unfortunately, this approach also means that it increases memory usage, as the window struct is pretty large by now. It's probably fine though.

The primary use case right now of the focus list is removing references to a closed window from seats that have focused it. I considered using weak references, like for example a generational list, for this, but considered the simple list approach simpler for now. If the memory usage per window object ever annoys me enough, I might come back to the generational list.

I've also had a minor additional project which is not programming related. A few of my dress shirts do not fit well at the collar. Unfortunately my arm-length to shoulder-width to neck-diameter ratio is not served well by off-the-rack dress shirts: If the collar fits, the sleeves will usually be too short and the shirt too narrow around the shoulders. Now, tailoring a dress shirt to fit better is almost trivial, except for the collar and the shoulders. In the past I brought a few shirts to a professional tailor, but I was not overly excited about the results. So today I tried a random idea I had and simply added an additional collar button, which closes it a bit tighter. I am not sure yet if I like the result though, it causes the collar to close oddly asymmetrical.

Articles from blogs I read (generated by openring)

Status update, November 2025

Hi! This month a lot of new features have added to the Goguma mobile IRC client. Hubert Hirtz has implemented drafts so that unsent text gets saved and network disconnections don’t disrupt users typing a message. He also enabled replying to one’s own messages…

emersion, November 16, 2025

the last couple years in v8's garbage collector

Let’s talk about memory management! Following up on my article about 5 years of developments in V8’s garbage collector, today I’d like to bring that up to date with what went down in V8’s GC over the last couple years.methodololologyI selected all of the …

wingolog, November 13, 2025

OpenAI employees… are you okay?

You might have seen an article making the rounds this week, about a young man who ended his life after ChatGPT encouraged him to do so. The chat logs are really upsetting. Someone two degrees removed from me took their life a few weeks ago. A close friend rel…

Drew DeVault's blog, November 8, 2025